/**
 * Copyright (C) 2001-2020 by RapidMiner and the contributors
 * 
 * Complete list of developers available at our web site:
 * 
 * http://rapidminer.com
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Affero General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License along with this program.
 * If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.io.process;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.w3c.dom.Element;

import com.rapidminer.Process;
import com.rapidminer.operator.ExecutionUnit;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.operator.UserData;


/**
 * {@link ProcessXMLFilter} to handle generated operators end exported processes in UsageStats.
 *
 * @author Peter Toth, Marco Boeck
 * @since 9.0.0
 */
public class ProcessOriginProcessXMLFilter implements ProcessXMLFilter {

	/**
	 * ProcessOriginState is a special {@link UserData} to indicate that an operator is generated not by the user but by
	 * some other means, e.g. by the AutoModel wizard, TurboPrep, or generally coming from a samples folder. The
	 * UserData is used to differentiate the generated operators from the user selected ones in UsageStats.
	 *
	 * @since 9.0.0
	 */
	public enum ProcessOriginState implements UserData<Object> {

		/**
		 * Generated by Auto Model (executed behind the scenes)
		 *
		 * @since 9.0.0
		 */
		GENERATED_AUTOMODEL("am_gen_"),

		/**
		 * Exported by Auto Model (user opened it)
		 *
		 * @since 9.0.0
		 */
		EXPORTED_AUTOMODEL("am_exp_"),

		/**
		 * Generated by Turbo Prep (executed behind the scenes)
		 *
		 * @since 9.0.0
		 */
		GENERATED_TURBOPREP("tp_gen_"),

		/**
		 * Exported by Turbo Prep (user opened it)
		 *
		 * @since 9.0.0
		 */
		EXPORTED_TURBOPREP("tp_exp_"),

		/**
		 * Generated by Model Ops / Deployments (executed behind the scenes)
		 *
		 * @since 9.5.0
		 */
		GENERATED_MODELOPS("model_ops_gen_"),

		/**
		 * Process coming from community samples (user opened it)
		 *
		 * @since 9.0.0
		 */
		GENERATED_COMMUNITY("community_"),

		/**
		 * Process coming from training (user opened it)
		 *
		 * @since 9.0.0
		 */
		GENERATED_TRAINING("training_"),

		/**
		 * Process coming from samples (user opened it)
		 *
		 * @since 9.0.0
		 */
		GENERATED_SAMPLE("sample_"),

		/**
		 * Process coming from a template in the starting dialog
		 *
		 * @since 9.0.0
		 */
		GENERATED_TEMPLATE("template_"),

		/**
		 * Process coming from a tutorial in the starting dialog or the operator help
		 *
		 * @since 9.0.0
		 */
		GENERATED_TUTORIAL("tutorial_"),

		/**
		 * Exported by Auto Model in the Cloud (user opened it)
		 *
		 * @since 9.0.2
		 */
		EXPORTED_AUTOMODEL_CLOUD("amc_exp_"),

		/**
		 * Loaded from a web URL which typically would be done by clicking on a rapidminer://process_url/... link.
		 *
		 * @since 9.6.0
		 */
		WEB_URL("web_url_");


		private String prefix;

		ProcessOriginState(String prefix) {
			this.prefix = prefix;
		}

		@Override
		public UserData<Object> copyUserData(Object newParent) {
			return this;
		}

		/**
		 * Returns the usage stats prefix.
		 *
		 * @return the prefix, never {@code null} or empty
		 */
		public String getPrefix() {
			return prefix;
		}

	}

	private static final String KEY_PROCESS_ORIGIN = "origin";

	/** No longer used as of 9.0.0. But still needed for older, stored AM processes to check against */
	private static final String XML_ATTRIBUTE_AUTOMODEL_LEGACY = "automodel";

	private static final String XML_ATTRIBUTE_PROCESS_ORIGIN = "origin";

	@Override
	public void operatorExported(final Operator op, final Element opElement) {
		ProcessOriginState processOriginState = getProcessOriginState(op);
		if (processOriginState != null) {
			opElement.setAttribute(XML_ATTRIBUTE_PROCESS_ORIGIN, processOriginState.name());
		}
	}

	@Override
	public void executionUnitExported(final ExecutionUnit process, final Element element) {}

	@Override
	public void operatorImported(final Operator op, final Element opElement) {
		// legacy AM processes
		String attributeValue = opElement.getAttribute(XML_ATTRIBUTE_AUTOMODEL_LEGACY);
		if (attributeValue != null && !attributeValue.isEmpty()) {
			try {
				ProcessOriginState processOriginState = ProcessOriginState.valueOf(attributeValue);
				op.setUserData(KEY_PROCESS_ORIGIN, processOriginState);
				return;
			} catch (IllegalArgumentException e) {
				// legacy
				if ("GENERATED".equals(attributeValue)) {
					op.setUserData(KEY_PROCESS_ORIGIN, ProcessOriginState.GENERATED_AUTOMODEL);
					return;
				} else if ("EXPORTED".equals(attributeValue)) {
					op.setUserData(KEY_PROCESS_ORIGIN, ProcessOriginState.EXPORTED_AUTOMODEL);
					return;
				}
				// otherwise, illegal value. Ignore as before.
			}
		}

		attributeValue = opElement.getAttribute(XML_ATTRIBUTE_PROCESS_ORIGIN);
		if (attributeValue != null && !attributeValue.isEmpty()) {
			try {
				ProcessOriginState processOriginState = ProcessOriginState.valueOf(attributeValue);
				op.setUserData(KEY_PROCESS_ORIGIN, processOriginState);
			} catch (IllegalArgumentException e) {
				// ignore
			}
		}
	}

	@Override
	public void executionUnitImported(final ExecutionUnit process, final Element element) {}

	/**
	 * Returns an ProcessOriginState if an operator is generated by or exported from tools like AutoModel wizard,
	 * TurboPrep, or if it is from any samples. Otherwise, returns null if it was added to the process in a "regular"
	 * way by the user.
	 *
	 * @param operator
	 * 		the operator, must not be {@code null}
	 * @return the origin, or {@code null} if it was not created by any special means
	 * @since 9.0.0
	 */
	public static ProcessOriginState getProcessOriginState(Operator operator) {
		return (ProcessOriginState) operator.getUserData(KEY_PROCESS_ORIGIN);
	}

	/**
	 * Marks all operators of the process as generated by or exported from tools like AutoModel wizard, TurboPrep, 
	 * or if it is from any samples. Doesn't do anything if the processOriginState is null.
	 *
	 * @param process
	 * 		the process, must not be {@code null}. All inner operators of the process will be marked
	 * @param processOriginState
	 * 		the origin, if {@code null}, does nothing
	 * @since 9.0.0
	 */
	public static void setProcessOriginState(Process process, ProcessOriginState processOriginState) {
		if (processOriginState != null) {
			for (Operator op : process.getAllOperators()) {
				op.setUserData(KEY_PROCESS_ORIGIN, processOriginState);
			}
		}
	}
	
	/**
	 * Marks an operator and its inner operators as generated by or exported from tools like AutoModel wizard, TurboPrep,
	 * or if it is from any samples. Doesn't do anything if the processOriginState is null.
	 *
	 * @param operator
	 * 		the operator, must not be {@code null}
	 * @param processOriginState
	 * 		the origin, if {@code null}, does nothing
	 * @since 9.0.0
	 */
	public static void setProcessOriginState(Operator operator, ProcessOriginState processOriginState) {
		if (processOriginState != null) {
			List<Operator> affectedOperators = new LinkedList<>(Collections.singletonList(operator));
			if (operator instanceof OperatorChain) {
				affectedOperators.addAll(((OperatorChain) operator).getAllInnerOperators());
			}
			
			for (Operator op : affectedOperators) {
				op.setUserData(KEY_PROCESS_ORIGIN, processOriginState);
			}
		}
	}

}
